#include "pch.hpp"
#include "CssClass.hpp"

#include <boostext/typedefs.hpp>

using namespace Engine;
using namespace Engine::Ui;




CssParseHead::CssParseHead()
	: type(Widgets::Widget)
	, state(WidgetState::Normal)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

CssParseHead::CssParseHead(Widgets::Type type, const std::string& name, WidgetState::Type state)
	: type(type)
	, name(name)
	, state(state)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////




CssClassIdentifier::CssClassIdentifier(Widgets::Type type, QString name)
	: type(type)
	, name(name)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

CssClassIdentifier::CssClassIdentifier(const CssParseHead& head)
	: type(type)
	, name(name)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool CssClassIdentifier::operator==(const CssClassIdentifier& that) const
{
	if(type == that.type)
		return true;

	if(name == that.name)
		return true;

	return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool CssClassIdentifier::operator<(const CssClassIdentifier& that) const
{
	if(type < that.type)
		return true;

	if(name < that.name)
		return true;

	return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




CssClass::CssClass()
	: m_type(Widgets::Widget)
	, m_name("*")
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

CssClass::CssClass(const AttributeMap& defaultAttributes)
	: m_type(Widgets::Widget)
	, m_name("*")
	, m_attributes(defaultAttributes)
{
	foreach(const AttributePair& state, defaultAttributes)
	{
		if(state.second.find(WidgetState::Normal) == state.second.end())
			BOOSTEXT_THROW(boostext::exception("Missing default state in attributes"));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

CssClass::CssClass(Widgets::Type type, boost::weak_ptr<CssClass> superDefault)
	: m_type(type)
	, m_name("*")
	, m_default(superDefault)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

CssClass::CssClass(QString name, Widgets::Type type, boost::weak_ptr<CssClass> _default)
	: m_type(type)
	, m_name(name)
	, m_default(_default)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

Widgets::Type CssClass::type() const
{
	return m_type;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

QString CssClass::name() const
{
	return m_name;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

const CssClass::Parents& CssClass::parents() const
{
	return m_parents;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void CssClass::merge(const CssParseClass& parsed, const attribute_factory& factory, Errors& errors)
{
	QMapIterator<Attributes::Type, std::string> i(parsed.attributes);
	while(i.hasNext())
	{
		i.next();
		boost::any v;

		// Errors are reported, but merging is continued anyways
		try
		{
			v = factory.create(i.key(), i.value());
		}
		catch(boostext::critical_exception)
		{
			throw;
		}
		catch(boostext::exception& e)
		{
			Error err;
			err.what = e.what();

			// TODO: Find out line & column of error in original parsed document
			err.line = 0;
			err.col  = 0;

			errors.push_back(err);
		}

		insert(i.key(), parsed.parents.last().state, v);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void CssClass::insert(const Attributes::Type& attr, const WidgetState::Type& state, const boost::any& value)
{
	AttributeMap::iterator i = m_attributes.find(attr);
	if(i == m_attributes.end())
	{
		// If that attribute is not here by default, we insert it
		i = m_attributes.insert(std::make_pair(attr, states())).first;
	}

	i->second.insert(std::make_pair(state, value));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

boost::weak_ptr<CssClass> CssClass::defaultClass() const
{
	return m_default;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

const CssClass::AttributeMap& CssClass::attributes() const
{
	return m_attributes;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




CssDocument::CssDocument()
	: m_defaultClass(boost::make_shared<CssClass>())
{
	m_classes << m_defaultClass;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

CssDocument::CssDocument(const AttributeMap& attr)
	: m_defaultClass(boost::make_shared<CssClass>(attr))
{
	m_classes << m_defaultClass;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void CssDocument::clear()
{
	m_classes.clear();
	m_defaultClass = boost::make_shared<CssClass>();
	m_classes << m_defaultClass;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

const CssDocument::CssClasses& CssDocument::classes() const
{
	return m_classes;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void CssDocument::insert(const CssParseClass& c, const CssClass::attribute_factory& factory, Errors& errors)
{
	const std::string& name = c.parents.last().name;
	const Widgets::Type& type  = c.parents.last().type;

	// Try to find that exact class
	CssClass::ptr d = find(name.c_str(), type);
	if(!d)
		BOOSTEXT_THROW(programming_error("Shouldn't happen"));

	// Merge values
	d->merge(c, factory, errors);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

CssClass::ptr CssDocument::find(const QString& name, Widgets::Type type)
{
	if(name == "*" && type == Widgets::Widget)
		return m_defaultClass;

	BOOST_FOREACH(const CssClass::ptr& c, m_classes)
	{
		if(name == c->name() && type == c->type())
			return c;
	}

	QString parentName = "*";
	Widgets::Type parentType = Widgets::None;
	if(name != "*")
		parentType = type;
	else
		parentType = Widgets::parent(type);

	auto cssClass = boost::make_shared<CssClass>(name, type, find(parentName, parentType));
	m_classes << cssClass;
	return cssClass;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
